---
title: clsADUserServices
summary: Active Directory user, group, and access-control maintenance for the internal and external domains, exposed as a single ASMX service class.
status: draft
owner: Not determined from source
last_reviewed: 2026-09-22
---

# clsADUserServices

`clsADUserServices` is the sole service class hosted by this repository. It provisions, modifies, unlocks, and deletes Active Directory user accounts; manages group membership; reads directory attributes (email, fax, printer, last logon, group/resource membership); and implements the service's own IP-allow-list access-control primitive (`CheckAccess`). All directory access is concentrated in three private helpers (`GetContext`, `GetUserPrincipal`, `GetGroupPrincipal`) shared by every operation. No consumer of this service is identifiable from the repository.

| Field | Value |
| --- | --- |
| **Service class** | `ADUserServices.clsADUserServices` |
| **Marker file** | `clsADUserServices.asmx` |
| **Codebehind** | `src\ADUserServices\clsADUserServices.asmx.vb` |
| **SOAP namespace** | `http://tempuri.org/` |
| **WSI conformance** | `WsiProfiles.BasicProfile1_1` |
| **Base class** | `System.Web.Services.WebService` |
| **ScriptService** | Commented out (not active) |
| **Constructor work** | None - no class constructor exists in the codebehind |
| **Shared state** | None process-wide; four `Private` **instance** fields live for one request only: `m_CredUserId`, `m_CredPass` (never assigned or read anywhere - dead), `m_FoundUser` (written/read only by dead code), `m_LastMessage` (written by the three directory helpers on exception, never read or returned by any operation) |
| **Class-level authorization** | None in code - every check is per-operation |
| **Operations** | 19 |
| **Also hosted in** | - (single host) |

<!-- One H2 section per operation, in declaration order. The heading text is the bare operation
     name; the anchor it produces is the target of every catalog-row link in ../operations.md. -->

---

## HelloWorld

Returns a constant greeting string; a connectivity/scaffolding check with no directory interaction.

| Field | Value |
| --- | --- |
| **Operation name** | `HelloWorld` |
| **Message name** | `HelloWorld` |
| **SOAP action** | `http://tempuri.org/HelloWorld` |
| **Implementing member** | `clsADUserServices.HelloWorld` (`src\ADUserServices\clsADUserServices.asmx.vb:86-89`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/hello-world.md](../operations/hello-world.md) |

### HelloWorld - Input message

No input parts - the request message is empty.

### HelloWorld - Output message

| Field | Value |
| --- | --- |
| **Return type** | `String` |
| **Response element** | `HelloWorldResult` |
| **Meaning** | A constant greeting string, `"Hello World"`, left over from Visual Studio ASMX project scaffolding |
| **Empty-result behavior** | Never empty - the literal is hard-coded and always returned |

### HelloWorld - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| - | Never | Cannot fail - no branching, no directory call | No |

### HelloWorld - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### HelloWorld - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | None |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result on every call |

### HelloWorld - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <HelloWorld xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <HelloWorldResponse xmlns="http://tempuri.org/">
      <HelloWorldResult>Hello World</HelloWorldResult>
    </HelloWorldResponse>
  </soap:Body>
</soap:Envelope>
```

---

## AddUser

Creates an Active Directory user account in the external domain and sets its attributes.

| Field | Value |
| --- | --- |
| **Operation name** | `AddUser` |
| **Message name** | `AddUser` |
| **SOAP action** | `http://tempuri.org/AddUser` |
| **Implementing member** | `clsADUserServices.AddUser` (`src\ADUserServices\clsADUserServices.asmx.vb:91-197`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) - `If (Not CheckAccess().HasAccess) Then Return AccessDenied()` ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/add-user.md](../operations/add-user.md) |

### AddUser - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inPassword` | `String` | Yes | - | Initial password; empty string substitutes a hard-coded default password literal (never reproduced here - see [tech-debt.md](../tech-debt.md) SEC-05) |
| `inName` | `String` | Yes | - | Directory `Name` attribute* |
| `inGivenName` | `String` | Yes | - | `GivenName`* |
| `inSurName` | `String` | Yes | - | `Surname`* |
| `inDescription` | `String` | Yes | - | `Description`* |
| `inPhoneNum` | `String` | Yes | - | `telephoneNumber` (written only when non-empty)* |
| `inMailAddr` | `String` | Yes | - | `mail` (written only when non-empty)* |
| `inTitle` | `String` | Yes | - | `title` (written only when non-empty)* |
| `inInfo` | `String` | Yes | - | `info` (written only when non-empty)* |
| `inCompany` | `String` | Yes | - | `company` (written only when non-empty)* |
| `inFAXNum` | `String` | Yes | - | `facsimileTelephoneNumber` (written only when non-empty)* |
| `inDivision` | `String` | Yes | - | `division` (written only when non-empty)* |

### AddUser - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `AddUserResult` |
| **Meaning** | Outcome of the account-creation attempt |
| **Empty-result behavior** | Not applicable - the structure is always populated |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | `""` on success; `"Access Denied"` on denial; `ex.Message` on a caught save/commit failure |
| `Response` | `EResponse` | No | `Success`, or `Failure` (denial, existing user [TD-17](../tech-debt.md), or a caught save/commit exception) |

### AddUser - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | `ADServiceResponse{Response=Failure, Message="Access Denied"}` | No - handled, no fault |
| Existing user | H2 finds an existing principal for `inUserId` | `ADServiceResponse{Response=Failure}` - `UserExists` is set then immediately overwritten ([TD-17](../tech-debt.md), unreachable on the wire) | No - handled, no fault |
| `userPrin.Save()` failure | AD rejects the new principal (policy, duplicate, connectivity) | `ADServiceResponse{Response=Failure, Message=ex.Message}` - execution **continues** into attribute writes on the unsaved principal ([TD-18](../tech-debt.md), **HIGH**) | Yes - `ex.Message` copied into `Message` |
| `dirEntry.CommitChanges()` failure | Attribute commit fails after a successful save | `ADServiceResponse{Response=Failure, Message=ex.Message}` | Yes - `ex.Message` copied into `Message` |
| Directory/context construction failure | Failure in `GetContext`/`New UserPrincipal`/property assignment, outside both `Try` blocks | `SoapException` produced by ASP.NET | Yes |

### AddUser - Authorization

Access-checked: the operation's first statement is `If (Not CheckAccess().HasAccess) Then Return AccessDenied()` ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### AddUser - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - writes a new `UserPrincipal`/`DirectoryEntry` object under `CN=Users,` + the external container in Active Directory |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | A second call with the same `inUserId` returns `Response=Failure` (the `UserExists` code is unreachable - [TD-17](../tech-debt.md)); the account is not modified |

### AddUser - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <AddUser xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inPassword><value></inPassword>
      <inName><value></inName>
      <inGivenName><value></inGivenName>
      <inSurName><value></inSurName>
      <inDescription><value></inDescription>
      <inPhoneNum><value></inPhoneNum>
      <inMailAddr><value></inMailAddr>
      <inTitle><value></inTitle>
      <inInfo><value></inInfo>
      <inCompany><value></inCompany>
      <inFAXNum><value></inFAXNum>
      <inDivision><value></inDivision>
    </AddUser>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <AddUserResponse xmlns="http://tempuri.org/">
      <AddUserResult>
        <Message></Message>
        <Response>Success</Response>
      </AddUserResult>
    </AddUserResponse>
  </soap:Body>
</soap:Envelope>
```

---

## UnlockAccount

Unlocks an Active Directory account in the external domain if it is currently locked out.

| Field | Value |
| --- | --- |
| **Operation name** | `UnlockAccount` |
| **Message name** | `UnlockAccount` |
| **SOAP action** | `http://tempuri.org/UnlockAccount` |
| **Implementing member** | `clsADUserServices.UnlockAccount` (`src\ADUserServices\clsADUserServices.asmx.vb:200-226`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/unlock-account.md](../operations/unlock-account.md) |

### UnlockAccount - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |

### UnlockAccount - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `UnlockAccountResult` |
| **Meaning** | Outcome of the unlock attempt |
| **Empty-result behavior** | Not applicable |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | Free-text wording distinguishing "unlocked", "was not locked", and "not unlocked" - this distinction is **not** carried in `Response` |
| `Response` | `EResponse` | No | `UserDoesNotExist`, `Success` (covers unlocked, already-unlocked, and unlock-did-not-take-effect), or `Failure` + `ex.Message` |

### UnlockAccount - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Any other exception | The whole body is wrapped in `Try`/`Catch ex As Exception` | Sentinel `Failure` + `ex.Message` | Yes - `ex.Message` copied into `Message` |

Never faults* - the entire operation body is inside a single `Try`/`Catch`.

### UnlockAccount - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### UnlockAccount - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - clears the AD lockout flag on the target principal when locked |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | A second call on an already-unlocked account returns `Success` with "was not locked" wording |

### UnlockAccount - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <UnlockAccount xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
    </UnlockAccount>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <UnlockAccountResponse xmlns="http://tempuri.org/">
      <UnlockAccountResult>
        <Message></Message>
        <Response>Success</Response>
      </UnlockAccountResult>
    </UnlockAccountResponse>
  </soap:Body>
</soap:Envelope>
```

---

## AddUserToGroup

Adds a user to a group in the external domain.

| Field | Value |
| --- | --- |
| **Operation name** | `AddUserToGroup` |
| **Message name** | `AddUserToGroup` |
| **SOAP action** | `http://tempuri.org/AddUserToGroup` |
| **Implementing member** | `clsADUserServices.AddUserToGroup` (`src\ADUserServices\clsADUserServices.asmx.vb:228-267`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/add-user-to-group.md](../operations/add-user-to-group.md) |

### AddUserToGroup - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inGroupName` | `String` | Yes | - | Target group's `SamAccountName`* |

### AddUserToGroup - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `AddUserToGroupResult` |
| **Meaning** | Outcome of the membership-add attempt |
| **Empty-result behavior** | Not applicable |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | `ex.Message` on a generic failure; otherwise empty |
| `Response` | `EResponse` | No | `UserDoesNotExist`, `GroupDoesNotExist`, `UserAlreadyInGroup` (detected via `ex.Message.Contains("already exists")`), `Success`, or `Failure` |

### AddUserToGroup - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Group not found | H3 returns `Nothing` | Sentinel `GroupDoesNotExist` | No |
| Already a member | `groupPrin.Members.Add`/`.Save()` throws an exception whose message contains `"already exists"` | Sentinel `UserAlreadyInGroup` - locale- and provider-fragile* | No |
| Any other exception | `groupPrin.Members.Add`/`.Save()` fails for another reason | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults.

### AddUserToGroup - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### AddUserToGroup - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - adds the user to the AD group's membership list |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | A second call returns `UserAlreadyInGroup` (subject to the English-substring detection caveat above) |

### AddUserToGroup - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <AddUserToGroup xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inGroupName><value></inGroupName>
    </AddUserToGroup>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <AddUserToGroupResponse xmlns="http://tempuri.org/">
      <AddUserToGroupResult>
        <Message></Message>
        <Response>Success</Response>
      </AddUserToGroupResult>
    </AddUserToGroupResponse>
  </soap:Body>
</soap:Envelope>
```

---

## DeleteUser

Deletes an Active Directory user account in the external domain.

| Field | Value |
| --- | --- |
| **Operation name** | `DeleteUser` |
| **Message name** | `DeleteUser` |
| **SOAP action** | `http://tempuri.org/DeleteUser` |
| **Implementing member** | `clsADUserServices.DeleteUser` (`src\ADUserServices\clsADUserServices.asmx.vb:270-295`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/delete-user.md](../operations/delete-user.md) |

### DeleteUser - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |

### DeleteUser - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `DeleteUserResult` |
| **Meaning** | Outcome of the deletion attempt |
| **Empty-result behavior** | A **missing user returns `Success`** - the delete is a silent no-op, indistinguishable from a directory-lookup failure inside `GetUserPrincipal` ([tech-debt.md](../tech-debt.md)) |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | `ex.Message` on a caught delete failure; otherwise empty |
| `Response` | `EResponse` | No | `Success` (deleted, or user absent/lookup failed) or `Failure` + `ex.Message` |

### DeleteUser - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| `userPrin.Delete()` failure | Directory rejects the delete | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |
| User not found / lookup failure | H2 returns `Nothing` | Sentinel `Success` (no-op) - the two cases are indistinguishable | No |

Never faults.

### DeleteUser - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### DeleteUser - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - removes the AD user object |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | A second call returns `Success` (no-op) regardless of whether the first call deleted the account |

### DeleteUser - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DeleteUser xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
    </DeleteUser>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DeleteUserResponse xmlns="http://tempuri.org/">
      <DeleteUserResult>
        <Message></Message>
        <Response>Success</Response>
      </DeleteUserResult>
    </DeleteUserResponse>
  </soap:Body>
</soap:Envelope>
```

---

## DeleteUserFromGroup

Removes a user from a group in the external domain.

| Field | Value |
| --- | --- |
| **Operation name** | `DeleteUserFromGroup` |
| **Message name** | `DeleteUserFromGroup` |
| **SOAP action** | `http://tempuri.org/DeleteUserFromGroup` |
| **Implementing member** | `clsADUserServices.DeleteUserFromGroup` (`src\ADUserServices\clsADUserServices.asmx.vb:297-340`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/delete-user-from-group.md](../operations/delete-user-from-group.md) |

### DeleteUserFromGroup - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inGroupName` | `String` | Yes | - | Target group's `SamAccountName`* |

### DeleteUserFromGroup - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `DeleteUserFromGroupResult` |
| **Meaning** | Outcome of the membership-removal attempt |
| **Empty-result behavior** | `Success` is returned for a genuine removal, for "user was not a member", **and** for a genuine directory failure - the three are indistinguishable ([tech-debt.md](../tech-debt.md) TD-20, **HIGH**) |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | Always empty on this path - the `Catch` block is an explicit swallow |
| `Response` | `EResponse` | No | `GroupDoesNotExist` or `Success` |

### DeleteUserFromGroup - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| Group not found | H3 returns `Nothing` | Sentinel `GroupDoesNotExist` | No |
| Any directory failure (including a genuine removal failure) | `deGroup.Invoke("Remove", New Object() {deUser.Path})`/`CommitChanges()` throws | **Caught and swallowed** - `Catch` block contains the authored comment `'gobble - user is pribably not in the group`; returns `Success` | No - the failure is never surfaced to the caller in any form |

Never faults. This is the most consequential fault-handling finding on this operation - see [tech-debt.md](../tech-debt.md) TD-20 and [faults.md](../faults.md).

### DeleteUserFromGroup - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### DeleteUserFromGroup - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - removes the user from the AD group's membership list via a `DirectoryEntry.Invoke("Remove", New Object() {deUser.Path})` call (dropped to `DirectoryEntry` deliberately because the `GroupPrincipal.Remove` method fails on large groups, per an authored comment) |
| Downstream calls | Active Directory via `System.DirectoryServices` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | A second call returns `Success` regardless of whether the user was ever a member |

### DeleteUserFromGroup - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DeleteUserFromGroup xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inGroupName><value></inGroupName>
    </DeleteUserFromGroup>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DeleteUserFromGroupResponse xmlns="http://tempuri.org/">
      <DeleteUserFromGroupResult>
        <Message></Message>
        <Response>Success</Response>
      </DeleteUserFromGroupResult>
    </DeleteUserFromGroupResponse>
  </soap:Body>
</soap:Envelope>
```

---

## DoesUserExist

Reports whether a user account exists in the requested domain.

| Field | Value |
| --- | --- |
| **Operation name** | `DoesUserExist` |
| **Message name** | `DoesUserExist` |
| **SOAP action** | `http://tempuri.org/DoesUserExist` |
| **Implementing member** | `clsADUserServices.DoesUserExist` (`src\ADUserServices\clsADUserServices.asmx.vb:342-353`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/does-user-exist.md](../operations/does-user-exist.md) |

### DoesUserExist - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects `external.saif.com` (`True`) vs `corp.saif.com` (`False`) |

### DoesUserExist - Output message

| Field | Value |
| --- | --- |
| **Return type** | `Boolean` |
| **Response element** | `DoesUserExistResult` |
| **Meaning** | Whether a matching principal was found |
| **Empty-result behavior** | Returns `False` both when the user is genuinely absent **and** when the directory lookup failed - the exception is swallowed inside `GetUserPrincipal` into the unread `m_LastMessage` field ([tech-debt.md](../tech-debt.md) TD-22) |

### DoesUserExist - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User absent or directory failure | H2 returns `Nothing` for either reason | `False` | No - the two cases are indistinguishable to the caller |

Never faults.

### DoesUserExist - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### DoesUserExist - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` (read-only) |
| Files or messages produced | None |
| Shared state touched | `m_LastMessage` - written on failure, never read by any caller-visible path |
| Repeat-call behavior | Identical result for the same input |

### DoesUserExist - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DoesUserExist xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inExternalDomain><value></inExternalDomain>
    </DoesUserExist>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DoesUserExistResponse xmlns="http://tempuri.org/">
      <DoesUserExistResult>true</DoesUserExistResult>
    </DoesUserExistResponse>
  </soap:Body>
</soap:Envelope>
```

---

## DoesUserHaveResource

Reports whether a user's authorization groups include a group named `inResourceName`.

| Field | Value |
| --- | --- |
| **Operation name** | `DoesUserHaveResource` |
| **Message name** | `DoesUserHaveResource` |
| **SOAP action** | `http://tempuri.org/DoesUserHaveResource` |
| **Implementing member** | `clsADUserServices.DoesUserHaveResource` (`src\ADUserServices\clsADUserServices.asmx.vb:355-386`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/does-user-have-resource.md](../operations/does-user-have-resource.md) |

### DoesUserHaveResource - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inResourceName` | `String` | Yes | - | Group name to match by equality against `userPrin.GetAuthorizationGroups()`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### DoesUserHaveResource - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `DoesUserHaveResourceResult` |
| **Meaning** | Whether the named group was found among the user's authorization groups |
| **Empty-result behavior** | Not applicable - `Message` always carries the literal `"True"` or `"False"` |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | No | Literal `"True"`/`"False"` - the answer travels in `Message`, not `Response` |
| `Response` | `EResponse` | No | `Success` (answer computed) or `UserDoesNotExist` |

### DoesUserHaveResource - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Directory failure inside `GetAuthorizationGroups` | No `Try`/`Catch` wraps the iteration | `SoapException` produced by ASP.NET | Yes |

### DoesUserHaveResource - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### DoesUserHaveResource - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` (read-only); an unused `GetGroupPrincipal` (H3) call is also made and its result discarded ([tech-debt.md](../tech-debt.md) TD-23) |
| Files or messages produced | None |
| Shared state touched | `m_FoundUser` reset to `False`, never read afterward on this path |
| Repeat-call behavior | Identical result for the same input |

### DoesUserHaveResource - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DoesUserHaveResource xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inResourceName><value></inResourceName>
      <inExternalDomain><value></inExternalDomain>
    </DoesUserHaveResource>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <DoesUserHaveResourceResponse xmlns="http://tempuri.org/">
      <DoesUserHaveResourceResult>
        <Message>True</Message>
        <Response>Success</Response>
      </DoesUserHaveResourceResult>
    </DoesUserHaveResourceResponse>
  </soap:Body>
</soap:Envelope>
```

---

## ChangeUserAccount

Updates directory attributes on an existing user account (either domain, selected internally by the operation).

| Field | Value |
| --- | --- |
| **Operation name** | `ChangeUserAccount` |
| **Message name** | `ChangeUserAccount` |
| **SOAP action** | `http://tempuri.org/ChangeUserAccount` |
| **Implementing member** | `clsADUserServices.ChangeUserAccount` (`src\ADUserServices\clsADUserServices.asmx.vb:388-469`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/change-user-account.md](../operations/change-user-account.md) |

### ChangeUserAccount - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inGivenName` | `String` | Yes | - | `GivenName` (written only when non-empty)* |
| `inSurName` | `String` | Yes | - | `Surname` (written only when non-empty)* |
| `inDescription` | `String` | Yes | - | `Description` (written only when non-empty)* |
| `inPhoneNum` | `String` | Yes | - | `telephoneNumber` (written only when non-empty)* |
| `inMailAddr` | `String` | Yes | - | `mail` (written only when non-empty)* |
| `inTitle` | `String` | Yes | - | `title` (written only when non-empty)* |
| `inInfo` | `String` | Yes | - | `info` (written only when non-empty; does **not** set the `modified` flag - [tech-debt.md](../tech-debt.md) TD-19)* |
| `inCompany` | `String` | Yes | - | `company` (written only when non-empty; does **not** set the `modified` flag - TD-19)* |
| `inFAXNum` | `String` | Yes | - | `facsimileTelephoneNumber` (written only when non-empty)* |

### ChangeUserAccount - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `ChangeUserAccountResult` |
| **Meaning** | Outcome of the attribute-update attempt |
| **Empty-result behavior** | A request that changes **only** `inCompany` and/or `inInfo` returns `Success` having committed nothing to the directory, because those two writes never set the `modified` flag that gates `de.CommitChanges()` ([tech-debt.md](../tech-debt.md) TD-19, **HIGH**) |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | `ex.Message` on a caught failure; otherwise empty |
| `Response` | `EResponse` | No | `UserDoesNotExist`, `Success`, or `Failure` + `ex.Message` |

### ChangeUserAccount - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| User not found | `UserPrincipal.FindByIdentity` returns `Nothing` (called directly, not through H2) | Sentinel `UserDoesNotExist` | No |
| Any exception inside the `Try` | `userPrin.Save()`/`de.CommitChanges()`/property assignment fails | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults. Unlike the other lookups this operation calls `FindByIdentity` directly rather than through H2, so a `Nothing` context (a directory failure, not just "user absent") would surface differently than in operations using H2.

### ChangeUserAccount - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### ChangeUserAccount - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - writes selected attributes on the AD user object |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Idempotent for the fields that do set `modified`; a repeat call touching only `inCompany`/`inInfo` never commits ([TD-19](../tech-debt.md)) |

### ChangeUserAccount - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserAccount xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inGivenName><value></inGivenName>
      <inSurName><value></inSurName>
      <inDescription><value></inDescription>
      <inPhoneNum><value></inPhoneNum>
      <inMailAddr><value></inMailAddr>
      <inTitle><value></inTitle>
      <inInfo><value></inInfo>
      <inCompany><value></inCompany>
      <inFAXNum><value></inFAXNum>
    </ChangeUserAccount>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserAccountResponse xmlns="http://tempuri.org/">
      <ChangeUserAccountResult>
        <Message></Message>
        <Response>Success</Response>
      </ChangeUserAccountResult>
    </ChangeUserAccountResponse>
  </soap:Body>
</soap:Envelope>
```

---

## ChangeUserPassword

Sets a new password for a user, swallowing any failure into the sentinel response.

| Field | Value |
| --- | --- |
| **Operation name** | `ChangeUserPassword` |
| **Message name** | `ChangeUserPassword` |
| **SOAP action** | `http://tempuri.org/ChangeUserPassword` |
| **Implementing member** | `clsADUserServices.ChangeUserPassword` (`src\ADUserServices\clsADUserServices.asmx.vb:471-491`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/change-user-password.md](../operations/change-user-password.md) |

### ChangeUserPassword - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inNewPassword` | `String` | Yes | - | New password value - never reproduce a real value in examples |

### ChangeUserPassword - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `ChangeUserPasswordResult` |
| **Meaning** | Outcome of the password-change attempt |
| **Empty-result behavior** | Not applicable |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | `ex.Message` on any caught failure (policy, complexity, history, connectivity) - flattened into free text |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, or `Failure` |

### ChangeUserPassword - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| Any exception from the private `ChangePassword` helper (H6) | Password rejected, user not found, or directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message`, raw .NET exception text |

Never faults.

### ChangeUserPassword - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### ChangeUserPassword - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - sets the AD password via `DirectoryEntry.Invoke("SetPassword", oPassword)` and `CommitChanges()` |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Each call sets the password again; no history-of-calls tracking |

### ChangeUserPassword - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserPassword xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inNewPassword><value></inNewPassword>
    </ChangeUserPassword>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserPasswordResponse xmlns="http://tempuri.org/">
      <ChangeUserPasswordResult>
        <Message></Message>
        <Response>Success</Response>
      </ChangeUserPasswordResult>
    </ChangeUserPasswordResponse>
  </soap:Body>
</soap:Envelope>
```

---

## ChangeUserPasswordOrGetException

Sets a new password for a user; the deliberately faulting counterpart of `ChangeUserPassword` - no exception handling.

| Field | Value |
| --- | --- |
| **Operation name** | `ChangeUserPasswordOrGetException` |
| **Message name** | `ChangeUserPasswordOrGetException` |
| **SOAP action** | `http://tempuri.org/ChangeUserPasswordOrGetException` |
| **Implementing member** | `clsADUserServices.ChangeUserPasswordOrGetException` (`src\ADUserServices\clsADUserServices.asmx.vb:493-501`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [Access-checked](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated) ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)) |
| **Process** | [operations/change-user-password-or-get-exception.md](../operations/change-user-password-or-get-exception.md) |

### ChangeUserPasswordOrGetException - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inNewPassword` | `String` | Yes | - | New password value - never reproduce a real value in examples |

### ChangeUserPasswordOrGetException - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `ChangeUserPasswordOrGetExceptionResult` |
| **Meaning** | Outcome of the password-change attempt |
| **Empty-result behavior** | Not applicable |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | No | `String.Empty` on success; not populated on failure - failure instead escapes as a fault |
| `Response` | `EResponse` | No | `Success` or `UserDoesNotExist` on the non-faulting paths |

### ChangeUserPasswordOrGetException - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Access denied | `CheckAccess().HasAccess` is `False` | Sentinel `Failure`/`"Access Denied"` | No |
| Any exception from the private `ChangePassword` helper (H6) | Password rejected, user not found, or directory error - **no `Try`/`Catch`** | `SoapException` produced by ASP.NET, carrying the .NET exception | Yes |

This operation is a deliberately faulting variant of `ChangeUserPassword`; the two are near-duplicates differing only in error strategy, and the naming does not make that difference discoverable from the WSDL ([tech-debt.md](../tech-debt.md) TD-24).

### ChangeUserPasswordOrGetException - Authorization

Access-checked ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)). See [Authentication and authorization](../authentication-authorization.md#sec-svc-005-checkaccess-itself-is-published-and-unauthenticated).

### ChangeUserPasswordOrGetException - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None (no database) - sets the AD password via `DirectoryEntry.Invoke("SetPassword", oPassword)` and `CommitChanges()` |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Each call sets the password again |

### ChangeUserPasswordOrGetException - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserPasswordOrGetException xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inNewPassword><value></inNewPassword>
    </ChangeUserPasswordOrGetException>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ChangeUserPasswordOrGetExceptionResponse xmlns="http://tempuri.org/">
      <ChangeUserPasswordOrGetExceptionResult>
        <Message></Message>
        <Response>Success</Response>
      </ChangeUserPasswordOrGetExceptionResult>
    </ChangeUserPasswordOrGetExceptionResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserEmail

Reads the `mail` attribute for a user.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserEmail` |
| **Message name** | `GetUserEmail` |
| **SOAP action** | `http://tempuri.org/GetUserEmail` |
| **Implementing member** | `clsADUserServices.GetUserEmail` (`src\ADUserServices\clsADUserServices.asmx.vb:526-554`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-email.md](../operations/get-user-email.md) |

### GetUserEmail - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### GetUserEmail - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `GetUserEmailResult` |
| **Meaning** | The AD `mail` value for the user |
| **Empty-result behavior** | An unset `mail` attribute surfaces as `Failure` + a .NET null-reference/cast message rather than as a distinct response code* |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | The `mail` attribute value, or `ex.Message` on failure |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, or `Failure` |

### GetUserEmail - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Any exception (including unset `mail`) | Late-bound `de.Properties.Item("mail").Value` assignment fails, or any directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults.

### GetUserEmail - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserEmail - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### GetUserEmail - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserEmail xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inExternalDomain><value></inExternalDomain>
    </GetUserEmail>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserEmailResponse xmlns="http://tempuri.org/">
      <GetUserEmailResult>
        <Message><value></Message>
        <Response>Success</Response>
      </GetUserEmailResult>
    </GetUserEmailResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserGroups

Lists the names of a user's authorization groups.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserGroups` |
| **Message name** | `GetUserGroups` |
| **SOAP action** | `http://tempuri.org/GetUserGroups` |
| **Implementing member** | `clsADUserServices.GetUserGroups` (`src\ADUserServices\clsADUserServices.asmx.vb:556-580`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-groups.md](../operations/get-user-groups.md) |

### GetUserGroups - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### GetUserGroups - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ArrayList` - untyped on the wire, serializes as unconstrained `xsd:anyType` ([tech-debt.md](../tech-debt.md) TD-05, **MEDIUM**); the implementation only ever adds `String` (`grp.Name`) |
| **Response element** | `GetUserGroupsResult` |
| **Meaning** | Group names the user belongs to, per `GetAuthorizationGroups()` |
| **Empty-result behavior** | An empty list means "no groups", "no such user", **or** "directory failure" - the **empty `Catch` block** ([tech-debt.md](../tech-debt.md) TD-21, **HIGH**) makes all three indistinguishable |

### GetUserGroups - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Any failure (user not found, directory error) | H2 fails, or `GetAuthorizationGroups()` throws | **Caught and swallowed** by an empty `Catch` block - returns the (possibly still-empty) `ArrayList` | No |

Never faults.

### GetUserGroups - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserGroups - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### GetUserGroups - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserGroups xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inExternalDomain><value></inExternalDomain>
    </GetUserGroups>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserGroupsResponse xmlns="http://tempuri.org/">
      <GetUserGroupsResult>
        <anyType xsi:type="xsd:string"><value></anyType>
      </GetUserGroupsResult>
    </GetUserGroupsResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserResources

Lists a subset of a user's authorization groups whose name marks them as a "resource", filtered by environment.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserResources` |
| **Message name** | `GetUserResources` |
| **SOAP action** | `http://tempuri.org/GetUserResources` |
| **Implementing member** | `clsADUserServices.GetUserResources` (`src\ADUserServices\clsADUserServices.asmx.vb:582-617`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-resources.md](../operations/get-user-resources.md) |

### GetUserResources - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inProdEnvironment` | `Boolean` | Yes | - | Selects the production-vs-test resource-name branch |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### GetUserResources - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ArrayList` - untyped on the wire ([tech-debt.md](../tech-debt.md) TD-05, **MEDIUM**); the implementation only ever adds `String` (`grp.Name`) |
| **Response element** | `GetUserResourcesResult` |
| **Meaning** | Names of groups matching `"Resource"`, filtered by `"TestResource"` and `inProdEnvironment` |
| **Empty-result behavior** | An empty list means "no matching groups", "no such user", **or** "directory failure" - same empty-`Catch` swallow as `GetUserGroups` ([tech-debt.md](../tech-debt.md) TD-21, **HIGH**). Also note: when `inProdEnvironment` is `True`, a `TestResource` group is silently skipped by an empty branch body |

### GetUserResources - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Any failure (user not found, directory error) | H2 fails, or `GetAuthorizationGroups()` throws | **Caught and swallowed** by an empty `Catch` block | No |

Never faults.

### GetUserResources - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserResources - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### GetUserResources - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserResources xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inProdEnvironment><value></inProdEnvironment>
      <inExternalDomain><value></inExternalDomain>
    </GetUserResources>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserResourcesResponse xmlns="http://tempuri.org/">
      <GetUserResourcesResult>
        <anyType xsi:type="xsd:string"><value></anyType>
      </GetUserResourcesResult>
    </GetUserResourcesResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserFAX

Reads the `facsimileTelephoneNumber` attribute for a user.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserFAX` |
| **Message name** | `GetUserFAX` |
| **SOAP action** | `http://tempuri.org/GetUserFAX` |
| **Implementing member** | `clsADUserServices.GetUserFAX` (`src\ADUserServices\clsADUserServices.asmx.vb:619-647`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-fax.md](../operations/get-user-fax.md) |

### GetUserFAX - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### GetUserFAX - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `GetUserFAXResult` |
| **Meaning** | The AD `facsimileTelephoneNumber` value for the user |
| **Empty-result behavior** | An unset attribute surfaces as `Failure` + a .NET exception message rather than as a distinct response code* |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | The `facsimileTelephoneNumber` value, or `ex.Message` on failure |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, or `Failure` |

### GetUserFAX - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Any exception | Late-bound property read fails, or any directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults.

### GetUserFAX - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserFAX - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### GetUserFAX - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserFAX xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inExternalDomain><value></inExternalDomain>
    </GetUserFAX>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserFAXResponse xmlns="http://tempuri.org/">
      <GetUserFAXResult>
        <Message><value></Message>
        <Response>Success</Response>
      </GetUserFAXResult>
    </GetUserFAXResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserLastLogon

Computes and reports a user's last-logon timestamp from `lastLogonTimestamp`.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserLastLogon` |
| **Message name** | `GetUserLastLogon` |
| **SOAP action** | `http://tempuri.org/GetUserLastLogon` |
| **Implementing member** | `clsADUserServices.GetUserLastLogon` (`src\ADUserServices\clsADUserServices.asmx.vb:649-685`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-last-logon.md](../operations/get-user-last-logon.md) |

### GetUserLastLogon - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### GetUserLastLogon - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `GetUserLastLogonResult` |
| **Meaning** | A `DateTime.ToString()` rendering of the last logon, derived from the `lastLogonTimestamp` FILETIME value |
| **Empty-result behavior** | A user with no `lastLogonTimestamp` attribute yields `Failure` + a .NET exception message rather than `InfoNotAvailable`*. The rendered timestamp string format depends on the server's current culture and time zone* |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | The computed last-logon `DateTime.ToString()`, or `ex.Message` on failure; empty for `InfoNotAvailable` |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, `InfoNotAvailable` (computed value `< 1`), or `Failure` |

### GetUserLastLogon - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Computed value `< 1` | The composed FILETIME value is less than 1 | Sentinel `InfoNotAvailable` (the authored comment references defaulting to Jan 1, 2000, but no such date is actually applied) | No |
| Any other exception | Late-bound COM property access (`HighPart`/`lowpart`) fails, or any directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults.

### GetUserLastLogon - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserLastLogon - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Result changes only as the underlying `lastLogonTimestamp` changes |

### GetUserLastLogon - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserLastLogon xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inExternalDomain><value></inExternalDomain>
    </GetUserLastLogon>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserLastLogonResponse xmlns="http://tempuri.org/">
      <GetUserLastLogonResult>
        <Message><value></Message>
        <Response>Success</Response>
      </GetUserLastLogonResult>
    </GetUserLastLogonResponse>
  </soap:Body>
</soap:Envelope>
```

---

## GetUserPrinter

Reads the `extensionAttribute2` attribute for a user in the internal domain only.

| Field | Value |
| --- | --- |
| **Operation name** | `GetUserPrinter` |
| **Message name** | `GetUserPrinter` |
| **SOAP action** | `http://tempuri.org/GetUserPrinter` |
| **Implementing member** | `clsADUserServices.GetUserPrinter` (`src\ADUserServices\clsADUserServices.asmx.vb:687-713`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/get-user-printer.md](../operations/get-user-printer.md) |

### GetUserPrinter - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* - **no domain parameter is offered; the operation is hard-wired to the internal domain** (authored comment `'Assume internal domain`) |

### GetUserPrinter - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `GetUserPrinterResult` |
| **Meaning** | The AD `extensionAttribute2` value for the user |
| **Empty-result behavior** | An unset attribute surfaces as `Failure` + a .NET exception message rather than as a distinct response code* |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | The `extensionAttribute2` value, or `ex.Message` on failure |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, or `Failure` |

### GetUserPrinter - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 (internal domain, `inExternalDomain = False`) returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Any exception | Late-bound property read fails, or any directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults.

### GetUserPrinter - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### GetUserPrinter - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory (internal domain only) via `System.DirectoryServices.AccountManagement` and `System.DirectoryServices` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### GetUserPrinter - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserPrinter xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
    </GetUserPrinter>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserPrinterResponse xmlns="http://tempuri.org/">
      <GetUserPrinterResult>
        <Message><value></Message>
        <Response>Success</Response>
      </GetUserPrinterResult>
    </GetUserPrinterResponse>
  </soap:Body>
</soap:Envelope>
```

---

## IsUserInGroup

Reports direct (non-nested) membership of a user in a group.

| Field | Value |
| --- | --- |
| **Operation name** | `IsUserInGroup` |
| **Message name** | `IsUserInGroup` |
| **SOAP action** | `http://tempuri.org/IsUserInGroup` |
| **Implementing member** | `clsADUserServices.IsUserInGroup` (`src\ADUserServices\clsADUserServices.asmx.vb:715-750`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) |
| **Process** | [operations/is-user-in-group.md](../operations/is-user-in-group.md) |

### IsUserInGroup - Input message

| Part | Type | Required | Default | Purpose |
| --- | --- | --- | --- | --- |
| `inUserId` | `String` | Yes | - | Target `sAMAccountName`* |
| `inGroupName` | `String` | Yes | - | Target group's `SamAccountName`* |
| `inExternalDomain` | `Boolean` | Yes | - | Selects the domain |

### IsUserInGroup - Output message

| Field | Value |
| --- | --- |
| **Return type** | `ADServiceResponse` |
| **Response element** | `IsUserInGroupResult` |
| **Meaning** | Whether `groupPrin.Members.Contains(userPrin)` is true - **direct membership only**, via `GroupPrincipal.Members.Contains`, unlike `GetUserGroups`/`GetUserResources`/`DoesUserHaveResource`, which walk `GetAuthorizationGroups()` and therefore include nested groups |
| **Empty-result behavior** | Not applicable - `Message` always carries the literal `"True"` or `"False"` on the success path |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `Message` | `String` | Yes | Literal `"True"`/`"False"`, or `ex.Message` on failure |
| `Response` | `EResponse` | No | `Success`, `UserDoesNotExist`, `GroupDoesNotExist`, or `Failure` |

### IsUserInGroup - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| User not found | H2 returns `Nothing` | Sentinel `UserDoesNotExist` | No |
| Group not found | H3 returns `Nothing` | Sentinel `GroupDoesNotExist` | No |
| Any other exception | Membership check throws, or any directory error | Sentinel `Failure` + `ex.Message` | Yes - via `Message` |

Never faults. Nested-group membership answers `"False"` even when the user is a member transitively, because `GetAuthorizationGroups()` is not used here.

### IsUserInGroup - Authorization

None in code. See [Authentication and authorization](../authentication-authorization.md).

### IsUserInGroup - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | Active Directory via `System.DirectoryServices.AccountManagement` (read-only) |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same input |

### IsUserInGroup - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <IsUserInGroup xmlns="http://tempuri.org/">
      <inUserId><value></inUserId>
      <inGroupName><value></inGroupName>
      <inExternalDomain><value></inExternalDomain>
    </IsUserInGroup>
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <IsUserInGroupResponse xmlns="http://tempuri.org/">
      <IsUserInGroupResult>
        <Message>True</Message>
        <Response>Success</Response>
      </IsUserInGroupResult>
    </IsUserInGroupResponse>
  </soap:Body>
</soap:Envelope>
```

---

## CheckAccess

The service's own access-control primitive: reports whether the caller's observed remote address is local or on the configured IP allow-list.

| Field | Value |
| --- | --- |
| **Operation name** | `CheckAccess` |
| **Message name** | `CheckAccess` |
| **SOAP action** | `http://tempuri.org/CheckAccess` |
| **Implementing member** | `clsADUserServices.CheckAccess` (`src\ADUserServices\clsADUserServices.asmx.vb:752-776`) |
| **Member kind** | `Function` - method with a return value |
| **Attributes** | `<WebMethod()>` |
| **Authorization** | [None in code](../authentication-authorization.md) - this operation **is itself** the access-control primitive consumed by the other eight operations, and it is published and unauthenticated |
| **Process** | [operations/check-access.md](../operations/check-access.md) |

### CheckAccess - Input message

No input parts - the request message is empty.

### CheckAccess - Output message

| Field | Value |
| --- | --- |
| **Return type** | `HasAccessResponse` |
| **Response element** | `CheckAccessResult` |
| **Meaning** | The access decision and its basis |
| **Empty-result behavior** | Not applicable - always populated when no fault occurs |

| Member | Type | Nullable | Purpose |
| --- | --- | --- | --- |
| `HasAccess` | `Boolean` | No | Overall decision - `True` if local or allow-listed |
| `Local` | `Boolean` | No | `True` when `HttpContext.Current.Request.IsLocal` |
| `EnvironmentApi` | `Boolean` | No | `True` when the incoming IP matched an allow-list entry |
| `RemoteAddress` | `String` | No | The caller's observed remote address (`HTTP_X_FORWARDED_FOR`, falling back to `REMOTE_ADDR`) - discloses the origin-check basis to any caller |

### CheckAccess - Faults

| Failure | Raised when | Surfaced as | Detail crosses the boundary |
| --- | --- | --- | --- |
| Environment-service failure | `GetAllowedIPs` (constructing `WebEnvironmentService` or its `.Split(",")` call) throws - **no `Try`/`Catch`** | `SoapException` produced by ASP.NET | Yes |

This is also the failure path that causes the eight gated operations to fault instead of returning the `Access Denied` sentinel - see [tech-debt.md](../tech-debt.md) TD-12.

### CheckAccess - Authorization

None in code. `CheckAccess` publishes the caller's observed remote address and the allow-list decision to any unauthenticated caller, functioning as an allow-list probing oracle. See [Authentication and authorization](../authentication-authorization.md).

### CheckAccess - Side effects

| Aspect | Finding |
| --- | --- |
| Records written | None |
| Downstream calls | `WebEnvironmentService` (`SAIF.Framework.Security`) to read the `aduserservices.allowedips` setting |
| Files or messages produced | None |
| Shared state touched | None |
| Repeat-call behavior | Identical result for the same caller address and allow-list configuration; re-reads the incoming IP on every allow-list comparison iteration rather than reusing the computed value ([tech-debt.md](../tech-debt.md) TD-09) |

### CheckAccess - Example message

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <CheckAccess xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>
```

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <CheckAccessResponse xmlns="http://tempuri.org/">
      <CheckAccessResult>
        <HasAccess>true</HasAccess>
        <Local>false</Local>
        <EnvironmentApi>true</EnvironmentApi>
        <RemoteAddress><value></RemoteAddress>
      </CheckAccessResult>
    </CheckAccessResponse>
  </soap:Body>
</soap:Envelope>
```

---

## Coverage checklist

| Member | `<WebMethod()>` | Documented section |
| --- | --- | --- |
| `String HelloWorld()` | Yes | [`HelloWorld`](#helloworld) |
| `ADServiceResponse AddUser(inUserId, inPassword, inName, inGivenName, inSurName, inDescription, inPhoneNum, inMailAddr, inTitle, inInfo, inCompany, inFAXNum, inDivision)` | Yes | [`AddUser`](#adduser) |
| `ADServiceResponse UnlockAccount(inUserId)` | Yes | [`UnlockAccount`](#unlockaccount) |
| `ADServiceResponse AddUserToGroup(inUserId, inGroupName)` | Yes | [`AddUserToGroup`](#addusertogroup) |
| `ADServiceResponse DeleteUser(inUserId)` | Yes | [`DeleteUser`](#deleteuser) |
| `ADServiceResponse DeleteUserFromGroup(inUserId, inGroupName)` | Yes | [`DeleteUserFromGroup`](#deleteuserfromgroup) |
| `Boolean DoesUserExist(inUserId, inExternalDomain)` | Yes | [`DoesUserExist`](#doesuserexist) |
| `ADServiceResponse DoesUserHaveResource(inUserId, inResourceName, inExternalDomain)` | Yes | [`DoesUserHaveResource`](#doesuserhaveresource) |
| `ADServiceResponse ChangeUserAccount(inUserId, inGivenName, inSurName, inDescription, inPhoneNum, inMailAddr, inTitle, inInfo, inCompany, inFAXNum)` | Yes | [`ChangeUserAccount`](#changeuseraccount) |
| `ADServiceResponse ChangeUserPassword(inUserId, inNewPassword)` | Yes | [`ChangeUserPassword`](#changeuserpassword) |
| `ADServiceResponse ChangeUserPasswordOrGetException(inUserId, inNewPassword)` | Yes | [`ChangeUserPasswordOrGetException`](#changeuserpasswordorgetexception) |
| `ADServiceResponse GetUserEmail(inUserId, inExternalDomain)` | Yes | [`GetUserEmail`](#getuseremail) |
| `ArrayList GetUserGroups(inUserId, inExternalDomain)` | Yes | [`GetUserGroups`](#getusergroups) |
| `ArrayList GetUserResources(inUserId, inProdEnvironment, inExternalDomain)` | Yes | [`GetUserResources`](#getuserresources) |
| `ADServiceResponse GetUserFAX(inUserId, inExternalDomain)` | Yes | [`GetUserFAX`](#getuserfax) |
| `ADServiceResponse GetUserLastLogon(inUserId, inExternalDomain)` | Yes | [`GetUserLastLogon`](#getuserlastlogon) |
| `ADServiceResponse GetUserPrinter(inUserId)` | Yes | [`GetUserPrinter`](#getuserprinter) |
| `ADServiceResponse IsUserInGroup(inUserId, inGroupName, inExternalDomain)` | Yes | [`IsUserInGroup`](#isuseringroup) |
| `HasAccessResponse CheckAccess()` | Yes | [`CheckAccess`](#checkaccess) |
| `Enum EResponse` | No | Not an operation - message type consumed by `ADServiceResponse`, not itself invocable |
| `Structure ADServiceResponse` | No | Not an operation - message type (response shape for most operations) |
| `Structure HasAccessResponse` | No | Not an operation - message type (response shape for `CheckAccess`) |
| `EResponse ChangePassword(inUserId, inNewPassword)` (Private) | No | Not an operation - private helper behind `ChangeUserPassword`/`ChangeUserPasswordOrGetException` |
| `ADServiceResponse AccessDenied()` (Private) | No | Not an operation - private helper that builds the `Failure`/`"Access Denied"` sentinel |
| `List(Of String) GetAllowedIPs()` (Private) | No | Not an operation - private helper reading the `aduserservices.allowedips` configuration setting |
| `String GetIncomingIp()` (Private) | No | Not an operation - private helper reading `HTTP_X_FORWARDED_FOR`/`REMOTE_ADDR` |
| `String GetLocalIpAddress()` (Private) | No | Not an operation - private helper, and dead code, never called |
| `PrincipalContext GetContext(inExternalDomain, [inContainer])` (Private) | No | Not an operation - private helper building the directory context |
| `UserPrincipal GetUserPrincipal(inUserId, inExternalDomain)` (Private) | No | Not an operation - private helper resolving a user principal |
| `GroupPrincipal GetGroupPrincipal(inGroupId, inExternalDomain)` (Private) | No | Not an operation - private helper resolving a group principal |
| `Sub FindUserInResource(ByRef inUserPrin As UserPrincipal, ByRef inGroupPrin As GroupPrincipal)` (Private) | No | Not an operation - private helper, and dead code, never called |

## Related documentation
- [Service index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Configuration](../configuration.md)
- [Dependencies](../dependencies.md)
- [Faults and error pipeline](../faults.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Sources
- `src\ADUserServices\clsADUserServices.asmx`
- `src\ADUserServices\clsADUserServices.asmx.vb`
- `.saifdocgen\state\runs\20260922-101326-aduserservices\analysis\aduserservices-analysis.md`
- `.saifdocgen\state\runs\20260922-101326-aduserservices\analysis\aduserservices-security-analysis.md`
